<?PHP
#
#   FILE:  AdvSysConfig.php
#
#   Part of the Metavus digital collections platform
#   Copyright 2017-2021 Edward Almasy and Internet Scout Research Group
#   http://metavus.net
#

use Metavus\FormUI;
use Metavus\SystemConfiguration;
use ScoutLib\ApplicationFramework;
use ScoutLib\Email;
use ScoutLib\StdLib;

# ----- LOCAL FUNCTIONS ------------------------------------------------------

/**
* Define fields for form.
* @return array Associative array of form field parameters, in the format
*       expected by FormUI.
*/
function DefineFormFields()
{
    # load up possible values for DefaultCharacterSet
    $CharSets = array(
        "ISO-8859-1" => "ISO-8859-1",
        "UTF-8" => "UTF-8"
    );

    # load up possible values for LoggingLevel
    $LoggingLevels = array(
        ApplicationFramework::LOGLVL_FATAL => "1 - Fatal",
        ApplicationFramework::LOGLVL_ERROR => "2 - Error",
        ApplicationFramework::LOGLVL_WARNING => "3 - Warning",
        ApplicationFramework::LOGLVL_INFO => "4 - Info",
        ApplicationFramework::LOGLVL_DEBUG => "5 - Debug",
        ApplicationFramework::LOGLVL_TRACE => "6 - Trace"
    );

    # get log file name
    $AF = ApplicationFramework::getInstance();
    $LogFileName = $AF->logFile();

    # set up editing form
    $FormFields = array(
        "HEADING-System" => array(
            "Type" => FormUI::FTYPE_HEADING,
            "Label" => "System",
        ),
        "SessionLifetime" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Session Lifetime",
            "Units" => "minutes",
            "MinVal" => 5,
            "RecVal" => 30,
            "Help" => "Length of time the site will remember a given user's "
                          ."login session. Users that are inactive longer than "
                          ."this will be logged out.",
        ),
        "PageCacheEnabled" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Enable Page Caching",
            "Help" => "Pages will be cached for anonymous users only.",
        ),
        "PageCacheExpirationPeriod" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Page Cache Expiration Period",
            "Units" => "minutes",
            "MinVal" => 1,
        ),
        "MaxExecutionTime" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Maximum Execution Time",
            "Units" => "minutes",
            "MinVal" => 1,
            "RecVal" => 5,
        ),
        "TemplateLocationCacheExpirationInterval" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Template Location Cache Expiration",
            "Units" => "minutes",
            "Help" => "Set to <i>0</i> to disable caching.",
            "MinVal" => 0,
        ),
        "ObjectLocationCacheExpirationInterval" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Object Location Cache Expiration",
            "Units" => "minutes",
            "Help" => "Set to <i>0</i> to disable caching.",
            "MinVal" => 0,
        ),
        "UseMinimizedJavascript" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Use Minimized JavaScript",
            "Help" => "When enabled, minimized JavaScript files (file"
                            ." name ending in <i>.min.js</i>) will be searched"
                            ." for and used if found.",
        ),
        "JavascriptMinimizationEnabled" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Generate Minimized JavaScript",
            "Help" => "When enabled, the application framework will"
                            ." attempt to generate minimized JavaScript files.",
        ),
        "UrlFingerprintingEnabled" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Add URL Fingerprints",
            "Help" => "When enabled, the appplication framework will"
                            ." attempt to insert timestamp-based fingerprints"
                            ." into the names of CSS, JavaScript, and image"
                            ." files, to help with browser caching.",
        ),
        "ScssSupportEnabled" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "SCSS Support Enabled",
            "Help" => "When enabled, the application framework will"
                            ." look for SCSS versions of included CSS files,"
                            ." compile them into CSS, and use the resulting"
                            ." CSS files.",
        ),
        "GenerateCompactCss" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Generate Compact CSS",
            "Help" => "When enabled, any CSS generated from SCSS files"
                            ." will be compacted, to improve page access speeds.",
        ),
        "DatabaseSlowQueryThresholdForForeground" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Database Slow Query Threshold (Foreground)",
            "Units" => "seconds",
            "Help" => "Database queries that take longer than this will"
                            ." be considered 'slow' by the database server, when"
                            ." running in the foreground.  (Minimum: <i>"
                            .ApplicationFramework::MIN_DB_SLOW_QUERY_THRESHOLD."</i>)",
            "MinVal" => ApplicationFramework::MIN_DB_SLOW_QUERY_THRESHOLD,
        ),
        "DatabaseSlowQueryThresholdForBackground" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Database Slow Query Threshold (Background)",
            "Units" => "seconds",
            "Help" => "Database queries that take longer than this will"
                            ." be considered 'slow' by the database server, when"
                            ." running in the background (i.e. in a queued task)."
                            ." (Minimum: <i>"
                            .ApplicationFramework::MIN_DB_SLOW_QUERY_THRESHOLD."</i>)",
            "MinVal" => ApplicationFramework::MIN_DB_SLOW_QUERY_THRESHOLD,
        ),
        "PreferHttpHost" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Prefer HTTP_HOST",
            "Help" => "If available, prefer <i>\$_SERVER[\"HTTP_HOST\"]</i>"
                            ." over <i>\$_SERVER[\"SERVER_NAME\"]</i> when"
                            ." determining the current URL.",
        ),
        "RootUrlOverride" => array(
            "Type" => FormUI::FTYPE_TEXT,
            "Label" => "Root URL Override",
            "Help" => "The \"root URL\" is the portion of the URL"
                            ." through the host name.  This setting primarily"
                            ." affects the values returned by the URL"
                            ." retrieval methods and the attempted insertion"
                            ." of clean URLs in outgoing HTML.",
            "ValidateFunction" => array("Metavus\\FormUI", "ValidateUrl"),
        ),
            # -------------------------------------------------
        "HEADING-Logging" => array(
            "Type" => FormUI::FTYPE_HEADING,
            "Label" => "Logging",
        ),
        "LoggingLevel" => array(
            "Type" => FormUI::FTYPE_OPTION,
            "Label" => "Logging Level",
            "Options" => $LoggingLevels,
            "OptionThreshold" => 1,
            "Help" => "Maximum level of messages recorded to ".$LogFileName,
        ),
        "LogSlowPageLoads" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Log Slow Page Loads",
        ),
        "SlowPageLoadThreshold" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Slow Page Threshold",
            "Units" => "seconds",
            "Help" => "Pages that take longer than this will be logged"
                            ." if <i>Log Slow Page Loads</i> is enabled.",
            "MinVal" => 2,
        ),
        "LogHighMemoryUsage" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Log High Memory Usage",
        ),
        "HighMemoryUsageThreshold" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "High Memory Usage Threshold",
            "Units" => "%",
            "Help" => "Pages that use more than this percentage of"
                            ." the PHP memory limit will be logged, if"
                            ." <i>Log High Memory Usage</i> is enabled.",
            "MinVal" => 10,
            "MaxVal" => 99,
            "RecVal" => 90,
        ),
        "LogPhpNotices" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Log PHP Notices",
            "Help" => "When enabled and the current logging level is"
                            ." set to <i>Warning</i> or above, PHP \"Notice\""
                            ." messages will be logged.",
        ),
        "DefaultCharacterSet" => array(
            "Type" => FormUI::FTYPE_OPTION,
            "Label" => "Default Character Encoding",
            "Options" => $CharSets,
            "Required" => true,
        ),
            # -------------------------------------------------
        "HEADING-PasswordRules" => array(
            "Type" => FormUI::FTYPE_HEADING,
            "Label" => "Passwords",
        ),
        "PasswordMinLength" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Minimum Password Length",
            "Units" => "characters",
            "MinVal" => 6,
            "Help" => "Passwords must contain at least this many characters."
                            ." (Minimum: <i>6</i>)",
        ),
        "PasswordUniqueChars" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Minimum Unique Characters",
            "Units" => "characters",
            "MinVal" => 4,
            "Help" => "Passwords must contain at least this many"
                            ." different characters. (Minimum: <i>4</i>)",
        ),
        "PasswordRequiresPunctuation" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Require Punctuation Character",
            "Help" => "When set, passwords must contain at least one"
                        ." punctuation character.",
        ),
        "PasswordRequiresMixedCase" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Require Mixed-Case Passwords",
            "Help" => "When set, passwords must contain both lower and"
                        ." upper case letters.",
        ),
        "PasswordRequiresDigits" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Require Digits",
            "Help" => "When set, passwords must contain at least one number.",
        ),
            # -------------------------------------------------
        "HEADING-Search" => array(
            "Type" => FormUI::FTYPE_HEADING,
            "Label" => "Search",
        ),
        "NumResourcesForSearchFacets" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "Resources for Facet Generation",
            "Units" => "resources",
            "Help" => "The maximum number of resources to use when"
                            ." generating search facets.",
        ),
            # -------------------------------------------------
        "HEADING-Mailing" => array(
            "Type" => FormUI::FTYPE_HEADING,
            "Label" => "Mailing",
        ),
        "MailingMethod" => array(
            "Type" => FormUI::FTYPE_OPTION,
            "Label" => "Mailing Method",
            "Options" => array(
                Email::METHOD_PHPMAIL => "PHP mail()",
                Email::METHOD_SMTP => "SMTP",
            ),
            "Help" => "Which mechanism to use when sending email.",
        ),
        "SmtpServer" => array(
            "Type" => FormUI::FTYPE_TEXT,
            "Label" => "SMTP Server",
            "Help" => "Example: <i>mail.myhost.com</i>",
            "DisplayIf" => array("MailingMethod" => EMail::METHOD_SMTP),
        ),
        "SmtpPort" => array(
            "Type" => FormUI::FTYPE_NUMBER,
            "Label" => "SMTP Port",
            "Help" => "Default: <i>25</i>",
            "DisplayIf" => array("MailingMethod" => EMail::METHOD_SMTP),
            "MinVal" => 1,
            "MaxVal" => 65535,
            "RecVal" => 25,
        ),
        "UseAuthenticationForSmtp" => array(
            "Type" => FormUI::FTYPE_FLAG,
            "Label" => "Use Authentication for SMTP",
            "DisplayIf" => array("MailingMethod" => EMail::METHOD_SMTP),
        ),
        "SmtpUserName" => array(
            "Type" => FormUI::FTYPE_TEXT,
            "Label" => "SMTP User Name",
            "Help" => "Only needed if using authentication for SMTP.",
            "DisplayIf" => array(
                "MailingMethod" => EMail::METHOD_SMTP,
                "UseAuthenticationForSmtp" => true
            ),
        ),
        "SmtpPassword" => array(
            "Type" => FormUI::FTYPE_TEXT,
            "Label" => "SMTP Password",
            "Help" => "Only needed if using authentication for SMTP.",
            "DisplayIf" => array(
                "MailingMethod" => EMail::METHOD_SMTP,
                "UseAuthenticationForSmtp" => true
            ),
        ),
        "EmailLineEnding" => array(
            "Type" => FormUI::FTYPE_OPTION,
            "Label" => "Email Line Ending",
            "Help" => "<i>CRLF</i> should be used whenever possible.",
            "Options" => array(
                "CRLF" => "CRLF",
                "CR" => "CR",
                "LF" => "LF",
            ),
        ),
    );

    # return form field info to caller
    return $FormFields;
}

/**
* Load values for form fields.
* @param array $FormFields Associative array of form field parameters, in
*       the format expected by FormUI.
* @return array Associative array of form field parameters, in the format
*       expected by FormUI, with field values filled in where available.
*/
function LoadFormValues($FormFields)
{
    $SysConfig = SystemConfiguration::getInstance();
    foreach ($FormFields as $FieldName => $FieldParams) {
        if ($FieldParams["Type"] == FormUI::FTYPE_HEADING) {
            continue;
        }
        unset($FieldValue);
        switch ($FieldName) {
            case "MailingMethod":
                $FieldValue = Email::DefaultDeliveryMethod();
                break;

            case "PageCacheExpirationPeriod":
                $FieldValue = $GLOBALS["AF"]->$FieldName() / 60;
                break;

            case "SmtpPassword":
                $FieldValue = Email::DefaultPassword();
                break;

            case "SmtpPort":
                $FieldValue = Email::DefaultPort();
                break;

            case "SmtpServer":
                $FieldValue = Email::DefaultServer();
                break;

            case "SmtpUserName":
                $FieldValue = Email::DefaultUserName();
                break;

            case "UseAuthenticationForSmtp":
                $FieldValue = Email::DefaultUseAuthentication();
                break;

            case "GenerateCompactCss":
            case "HighMemoryUsageThreshold":
            case "JavascriptMinimizationEnabled":
            case "LoggingLevel":
            case "LogHighMemoryUsage":
            case "LogPhpNotices":
            case "LogSlowPageLoads":
            case "ObjectLocationCacheExpirationInterval":
            case "PageCacheEnabled":
            case "ScssSupportEnabled":
            case "SlowPageLoadThreshold":
            case "DatabaseSlowQueryThresholdForForeground":
            case "DatabaseSlowQueryThresholdForBackground":
            case "TemplateLocationCacheExpirationInterval":
            case "UrlFingerprintingEnabled":
            case "UseMinimizedJavascript":
                $FieldFn = lcfirst($FieldName);
                $FieldValue = $GLOBALS["AF"]->$FieldFn();
                break;
            case "SessionLifetime":
            case "MaxExecutionTime":
                $FieldFn = lcfirst($FieldName);
                # convert the time loaded from seconds to minutes
                $FieldValue = ($GLOBALS["AF"]->$FieldFn()) / 60;
                break;

            default:
                if (!isset($FormFields[$FieldName]["Value"])) {
                    # retrieve field values from SystemConfiguration where available
                    if ($SysConfig->fieldExists($FieldName)) {
                        switch ($SysConfig->getFieldType($FieldName)) {
                            case SystemConfiguration::TYPE_ARRAY:
                                $FieldValue = $SysConfig->getArray($FieldName);
                                break;

                            case SystemConfiguration::TYPE_BOOL:
                                $FieldValue = $SysConfig->getBool($FieldName);
                                break;

                            case SystemConfiguration::TYPE_DATETIME:
                                $FieldValue = $SysConfig->getDatetime($FieldName);
                                break;

                            case SystemConfiguration::TYPE_INT:
                                $FieldValue = $SysConfig->getInt($FieldName);
                                break;

                            case SystemConfiguration::TYPE_STRING:
                            case SystemConfiguration::TYPE_EMAIL:
                            case SystemConfiguration::TYPE_IPADDRESS:
                            case SystemConfiguration::TYPE_URL:
                                $FieldValue = $SysConfig->getString($FieldName);
                                break;

                            case SystemConfiguration::TYPE_FLOAT:
                                $FieldValue = $SysConfig->getFloat($FieldName);
                                break;
                        }
                    } else {
                        throw new Exception("Configuration setting for which value"
                                ." is not available (".$FieldName.").");
                    }
                }
                break;
        }
        if (isset($FieldValue)) {
            $FormFields[$FieldName]["Value"] = $FieldValue;
        }
    }

    return $FormFields;
}

/**
* Check new email delivery settings, logging errors to FormUI if found.
* @param array $NewValues New setting values, with field names for index.
* @return bool TRUE if problems were found with settings, otherwise FALSE.
*/
function ValidateEmailDeliverySettings($NewValues)
{
    # save new settings for testing
    Email::DefaultDeliveryMethod($NewValues["MailingMethod"]);
    Email::DefaultPassword(isset($NewValues["SmtpPassword"]) ?
        $NewValues["SmtpPassword"] : null);
    Email::DefaultPort(isset($NewValues["SmtpPort"]) ?
        $NewValues["SmtpPort"] : null);
    Email::DefaultServer(isset($NewValues["SmtpServer"]) ?
        $NewValues["SmtpServer"] : null);
    Email::DefaultUserName(isset($NewValues["SmtpUserName"]) ?
        $NewValues["SmtpUserName"] : null);
    Email::DefaultUseAuthentication(isset($NewValues["UseAuthenticationForSmtp"]) ?
        $NewValues["UseAuthenticationForSmtp"] : null);

    $TestEmail = new Email();
    # if new settings do not test out okay
    if (!$TestEmail->DeliverySettingsOkay()) {
        # log error messages
        $Errs = Email::DeliverySettingErrors();
        if (in_array("UseAuthentication", $Errs)
                || in_array("UserName", $Errs)
                || in_array("Password", $Errs)) {
            $Msg = "Unable to connect with the specified <b>SMTP User Name</b>"
                    . " and <b>SMTP Password</b>. Please check that these"
                    . " values are correct to connect to <i>"
                    . $NewValues["SmtpServer"] . "</i>.";
            FormUI::LogError($Msg, "SmtpUserName");
            FormUI::LogError($Msg, "SmtpPassword");
        } elseif (in_array("Server", $Errs)
                || in_array("Port", $Errs)) {
            $Msg = "An error was found with the <b>SMTP Server</b> or"
                    ." <b>SMTP Port</b> number.  Please check that these values"
                    ." are correct.";
            FormUI::LogError($Msg, "SmtpServer");
            FormUI::LogError($Msg, "SmtpPort");
        } elseif (in_array("TLS", $Errs)) {
            $Msg = "An error was encountered trying to make a TLS connection to"
                    ." the specified server for SMTP.  Please check the server"
                    ." and port values to make sure they are correct.";
            FormUI::LogError($Msg, "SmtpServer");
        } else {
            $Msg = "An unknown error was encountered while trying to verify the"
                    ." <b>Mailing</b> settings.";
            FormUI::LogError($Msg);
        }

        # report to caller that problems were found
        return true;
    }

    # report to caller that no problems were found
    return false;
}

/**
* Save values from form fields.
* @param array $NewSettings Associative array with field names for the index
*       and field values for the values.
*/
function SaveFormValues($NewSettings)
{
    $SysConfig = SystemConfiguration::getInstance();
    foreach ($NewSettings as $FieldName => $NewFieldValue) {
        switch ($FieldName) {
            case "MailingMethod":
                Email::DefaultDeliveryMethod($NewFieldValue);
                break;

            case "PageCacheExpirationPeriod":
                $GLOBALS["AF"]->$FieldName(($NewFieldValue * 60), true);
                break;

            case "SmtpPassword":
                Email::DefaultPassword($NewFieldValue);
                break;

            case "SmtpPort":
                Email::DefaultPort($NewFieldValue);
                break;

            case "SmtpServer":
                Email::DefaultServer($NewFieldValue);
                break;

            case "SmtpUserName":
                Email::DefaultUserName($NewFieldValue);
                break;

            case "UseAuthenticationForSmtp":
                Email::DefaultUseAuthentication($NewFieldValue);
                break;

            case "GenerateCompactCss":
            case "HighMemoryUsageThreshold":
            case "JavascriptMinimizationEnabled":
            case "LoggingLevel":
            case "LogHighMemoryUsage":
            case "LogPhpNotices":
            case "LogSlowPageLoads":
            case "ObjectLocationCacheExpirationInterval":
            case "PageCacheEnabled":
            case "ScssSupportEnabled":
            case "SlowPageLoadThreshold":
            case "DatabaseSlowQueryThresholdForForeground":
            case "DatabaseSlowQueryThresholdForBackground":
            case "TemplateLocationCacheExpirationInterval":
            case "UrlFingerprintingEnabled":
            case "UseMinimizedJavascript":
                # save value via matching ApplicationFramework method
                $FieldFn = lcfirst($FieldName);
                $GLOBALS["AF"]->$FieldFn($NewFieldValue, true);
                break;
            case "SessionLifetime":
            case "MaxExecutionTime":
                # save value via matching ApplicationFramework method
                $FieldFn = lcfirst($FieldName);
                # convert the time to save from minutes to seconds
                $GLOBALS["AF"]->$FieldFn($NewFieldValue * 60, true);
                break;

            default:
                # save values via SystemConfiguration method if available
                if ($SysConfig->fieldExists($FieldName)) {
                    switch ($SysConfig->getFieldType($FieldName)) {
                        case SystemConfiguration::TYPE_ARRAY:
                            $SysConfig->setArray($FieldName, $NewFieldValue);
                            break;

                        case SystemConfiguration::TYPE_BOOL:
                            $SysConfig->setBool($FieldName, $NewFieldValue);
                            break;

                        case SystemConfiguration::TYPE_DATETIME:
                            $SysConfig->setDatetime($FieldName, $NewFieldValue);
                            break;

                        case SystemConfiguration::TYPE_INT:
                            $SysConfig->setInt($FieldName, (int) $NewFieldValue);
                            break;

                        case SystemConfiguration::TYPE_STRING:
                        case SystemConfiguration::TYPE_EMAIL:
                        case SystemConfiguration::TYPE_IPADDRESS:
                        case SystemConfiguration::TYPE_URL:
                            $SysConfig->setString($FieldName, $NewFieldValue);
                            break;

                        case SystemConfiguration::TYPE_FLOAT:
                            $SysConfig->setFloat($FieldName, (float) $NewFieldValue);
                            break;
                    }
                } else {
                    throw new Exception("New configuration value for which"
                            ." setting is not available (".$FieldName.").");
                }
                break;
        }
    }

    # save email delivery settings that were set above
    $SysConfig->setString("EmailDeliverySettings", Email::DefaultDeliverySettings());
}

# ----- MAIN -----------------------------------------------------------------

# check permissions
CheckAuthorization(PRIV_SYSADMIN, PRIV_COLLECTIONADMIN);

# set up form
$FormFields = DefineFormFields();

# load up current values for form fields
$FormFields = LoadFormValues($FormFields);

# instantiate form UI
$H_FormUI = new FormUI($FormFields);

# act on any button push
$ButtonPushed = StdLib::getFormValue("Submit");
switch ($ButtonPushed) {
    case "Save":
        # check values and bail out if any are invalid
        if ($H_FormUI->ValidateFieldInput()) {
            return;
        }

        # retrieve values from form
        $NewSettings = $H_FormUI->GetNewValuesFromForm();

        # check mail delivery values and bail out if any are invalid
        if (ValidateEmailDeliverySettings($NewSettings)) {
            return;
        }

        # save updated values
        SaveFormValues($NewSettings);

        # return to admin menu page
        $GLOBALS["AF"]->SetJumpToPage("SysAdmin");
        break;

    case "Cancel":
        # return to admin menu page
        $GLOBALS["AF"]->SetJumpToPage("SysAdmin");
        break;
}
